package com.ctg.itrdc.imageconsole.service.impl;

import java.text.MessageFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.ctg.itrdc.imageconsole.common.constants.AutoElasticConstant;
import com.ctg.itrdc.imageconsole.common.constants.SystemNoticeConstant;
import com.ctg.itrdc.imageconsole.common.util.PropertiesUtils;
import com.ctg.itrdc.imageconsole.common.util.TimeUtils;
import com.ctg.itrdc.imageconsole.data.dao.IAutoscalePolicyDao;
import com.ctg.itrdc.imageconsole.data.dao.IDockerMicroServiceDao;
import com.ctg.itrdc.imageconsole.data.dao.ISystemNoticeDao;
import com.ctg.itrdc.imageconsole.data.entity.AutoscalePolicy;
import com.ctg.itrdc.imageconsole.data.entity.DockerMicroService;
import com.ctg.itrdc.imageconsole.data.entity.SystemNotice;
import com.ctg.itrdc.imageconsole.service.IAutoElasticService;
import com.ctg.itrdc.imageconsole.util.KubeUtils;
import com.ctg.itrdc.imageconsole.util.QuartzUtils;
import com.ctg.itrdc.imageconsole.util.RestClientUtils;
import com.ctg.itrdc.imageconsole.util.RestOperation;

/**
 * 
 * @Description 自动伸缩
 * @author wupiao
 * @since 2017年2月16日
 */

@Component("autoElasticService")
public class AutoElasticServiceImpl implements IAutoElasticService, Job {

	private Logger logger = LoggerFactory.getLogger(this.getClass());
	private static final String NOTICE_TYPE = "autoScale";
	private static final String NOTICE_TYPE_NAME = "自动伸缩";
	private static final String ELASTIC_TYPE_EXPAND = "expand";
	private static final String ELASTIC_TYPE_NARROW = "narrow";

	@Resource
	private ISystemNoticeDao systemNoticeDao;
	@Resource
	private IAutoscalePolicyDao autoscalePolicyDao;
	@Resource
	private IDockerMicroServiceDao microServiceDao;

	@Override
	public List<DockerMicroService> getNeedAutoScaleServices() {
		DockerMicroService microService = new DockerMicroService();
		microService.setIsAutoScalable(AutoElasticConstant.AUTO_ELASTIC);
		microService.setState(AutoElasticConstant.RUNNING_STATE);

		return microServiceDao.selectByExample(microService);
	}

	@Override
	public void startAutoElastic(String apiServerUrl, String namespace
			, String dpName, AutoscalePolicy policy, Long createUserId) {
		if (AutoElasticConstant.ELASTIC_INDICATOR_TIME.equals(policy.getElasticIndicator())) {
			// 指标为时间周期
			startAutoElasticJobForTime(apiServerUrl, namespace, dpName, policy, createUserId);
		} else if (AutoElasticConstant.ELASTIC_INDICATOR_CPU.equals(policy.getElasticIndicator())) {
			// 指标为CPU
			startAutoElasticByCpu(apiServerUrl, namespace, dpName, policy, createUserId);
		}
	}

	@Override
	public void startAutoElasticJobForTime(String apiServerUrl, String namespace
			, String dpName, AutoscalePolicy policy, Long createUserId) {
		// 下面需要获取2个cron表达式，并且启动2个定时任务。
		// 因为根据时间周期进行伸缩的目的是 定时在一段时间内扩容，过了这段时间得缩回来。
		logger.info("启动了一个定时任务对rc进行自动伸缩（扩容）, apiServerUrl={}, 服务名:{}", apiServerUrl, dpName);
		// cron1表示以一定周期 定时扩容
		String cron1 = getCron(policy.getTimeRate(), policy.getElasticStartTime());
		Date elasticStartDate = TimeUtils.parse(policy.getElasticStartTime());
		if (elasticStartDate.getTime() < TimeUtils.systemTime().getTime()) {
			return;
		}
		JobDataMap jobDataMap = new JobDataMap();
		jobDataMap.put("createUserId", createUserId);
		jobDataMap.put("systemNoticeDao", systemNoticeDao);
		jobDataMap.put("apiServerUrl", apiServerUrl);
		jobDataMap.put("namespace", namespace);
		jobDataMap.put("dpName", dpName);
		jobDataMap.put("podNum", policy.getMaxPodNum());
		// 伸缩类型
		jobDataMap.put("elasticType", ELASTIC_TYPE_EXPAND);
		QuartzUtils.addJob("job_" + dpName + "_1", jobDataMap, AutoElasticServiceImpl.class, cron1, elasticStartDate);

		logger.info("启动了一个定时任务对rc进行自动伸缩（缩回到原来）, apiServerUrl={}, 服务名:{}", apiServerUrl, dpName);
		// cron2表示以一定周期 定时缩容，即还原回原来的实例数
		String cron2 = getCron(policy.getTimeRate(), policy.getElasticEndTime());
		Date elasticEndDate = TimeUtils.parse(policy.getElasticEndTime());
		jobDataMap.put("podNum", policy.getMinPodNum());
		jobDataMap.put("elasticType", ELASTIC_TYPE_NARROW);
		QuartzUtils.addJob("job_" + dpName + "_2", jobDataMap, AutoElasticServiceImpl.class, cron2, elasticEndDate);
	}

	@Override
	public void modifyAutoElastic(String apiServerUrl, String namespace
			, String dpName, AutoscalePolicy policy, Long createUserId) {
		String jobName1 = "job_" + dpName + "_1";
		String jobName2 = "job_" + dpName + "_2";
		// 如果此服务已经开启了以时间周期为指标的自动伸缩，则停止它
		if (QuartzUtils.isJobExist(jobName1)) {
			QuartzUtils.removeJob(jobName1);
		}
		if (QuartzUtils.isJobExist(jobName2)) {
			QuartzUtils.removeJob(jobName2);
		} else {
			// 如果之前是开启了以cpu为指标的自动伸缩，则删除之前以cpu为指标的自动伸缩
			delAutoScaleForCpu(apiServerUrl, dpName);
		}
		if (AutoElasticConstant.ELASTIC_INDICATOR_TIME.equals(policy.getElasticIndicator())) {
			startAutoElasticJobForTime(apiServerUrl, namespace, dpName, policy, createUserId);
			logger.info("修改了一个自动伸缩，指标为时间周期，服务名:{}", dpName);
		} else if (AutoElasticConstant.ELASTIC_INDICATOR_CPU.equals(policy.getElasticIndicator())) {
			// 重新开启一个以cpu为指标的自动伸缩任务
			startAutoElasticByCpu(apiServerUrl, namespace, dpName, policy, createUserId);
			logger.info("修改了一个自动伸缩，指标为cpu，服务名:{}", dpName);
		}
	}

	@Override
	public void stopAutoElastic(String apiServerUrl, String namespace
			, String dpName, AutoscalePolicy policy, Long createUserId) {
		if (AutoElasticConstant.ELASTIC_INDICATOR_TIME.equals(policy.getElasticIndicator())) {
			stopAutoElasticForTime(apiServerUrl, dpName);
			saveNotice(createUserId, "成功删除了指标为时间周期的自动伸缩任务, 服务名:" + dpName);
		} else if (AutoElasticConstant.ELASTIC_INDICATOR_CPU.equals(policy.getElasticIndicator())) {
			boolean isSuccess = delAutoScaleForCpu(apiServerUrl, dpName);
			if (isSuccess) {
				saveNotice(createUserId, "成功删除了指标为CPU的自动伸缩任务, 服务名:" + dpName);
			} else {
				saveNotice(createUserId, "删除指标为CPU的自动伸缩任务失败了, 服务名:" + dpName);
			}
		}
	}

	/**
	 * 停止以时间周期为指标而启动的定时任务
	 *  
	 * @param apiServerUrl
	 * @param dpName
	 * @return void
	 * @author wupiao
	 * @since 2017年3月30日
	 */
	private void stopAutoElasticForTime(String apiServerUrl, String dpName) {
		logger.info("停止了一个对rc进行自动伸缩（扩容）的定时任务, apiServerUrl={}, 服务名:{}", apiServerUrl, dpName);
		QuartzUtils.removeJob("job_" + dpName + "_1");

		logger.info("停止了一个对rc进行自动伸缩（缩回到原来）的定时任务, apiServerUrl={}, 服务名:{}", apiServerUrl, dpName);
		QuartzUtils.removeJob("job_" + dpName + "_2");
	}

	/**
	 * 删除以cpu为指标而启动的自动伸缩
	 * 
	 * @param policy
	 * @return void
	 * @author wupiao
	 * @since 2017年2月19日
	 */
	private boolean delAutoScaleForCpu(String apiServerUrl, String dpName) {
		String autoScaleName = "autoscale-" + dpName;

		String urlString = PropertiesUtils.getConfigValueCache("k8s.properties", "deleteAutoScale.api");
		urlString = MessageFormat.format(urlString, "http://" + apiServerUrl, autoScaleName);
		try {
			RestClientUtils.connect(urlString, new RestOperation() {

				@Override
				public Response request(WebTarget target) {
					return target.request().delete();
				}
			});
			return true;
		} catch (Exception e) {
			logger.error("停止以cpu为指标而启动的自动伸缩时  出现异常！", e);
			return false;
		}
	}


	/**
	 * 根据时间频率，时间撮 得到cron表达式
	 * 
	 * @param timeRate
	 * @param time
	 * @return
	 * @return String
	 * @author wupiao
	 * @since 2017年2月16日
	 */
	private String getCron(String timeRate, String time) {
		Date date = TimeUtils.parse(time);
		String year = TimeUtils.getYear(date);
		Integer month = TimeUtils.getMonth(date);
		Integer day = TimeUtils.getDay(date);
		Integer hour = TimeUtils.getHour(date);
		Integer minute = TimeUtils.getMinute(date);
		Integer second = TimeUtils.getSecond(date);
		String week = TimeUtils.getWeekOfDate(date);
		if (AutoElasticConstant.TIME_RATE_ONCE.equals(timeRate)) {
			return second + " " + minute + " " + hour + " " + day + " " + month + " ? " + year;
		} else if (AutoElasticConstant.TIME_RATE_DAY.equals(timeRate)) {
			return second + " " + minute + " " + hour + " * * ? *";
		} else if (AutoElasticConstant.TIME_RATE_WEEK.equals(timeRate)) {
			return second + " " + minute + " " + hour + " ? * " + week + " *";
		} else if (AutoElasticConstant.TIME_RATE_MONTH.equals(timeRate)) {
			return second + " " + minute + " " + hour + " " + day + " * ? *";
		} else if (AutoElasticConstant.TIME_RATE_YEAR.equals(timeRate)) {
			return second + " " + minute + " " + hour + " " + day + " " + month + " ? *";
		} else {
			return null;
		}
	}

	@Override
	public void execute(JobExecutionContext arg0) throws JobExecutionException {
		ISystemNoticeDao systemNoticeDao = (ISystemNoticeDao) arg0.getJobDetail().getJobDataMap()
				.get("systemNoticeDao");
		Long createUserId = (Long) arg0.getJobDetail().getJobDataMap().get("createUserId");
		String apiServerUrl = (String) arg0.getJobDetail().getJobDataMap().get("apiServerUrl");
		String namespace = (String) arg0.getJobDetail().getJobDataMap().get("namespace");
		String dpName = (String) arg0.getJobDetail().getJobDataMap().get("dpName");
		Integer podNum = (Integer) arg0.getJobDetail().getJobDataMap().get("podNum");
		String elasticType = (String) arg0.getJobDetail().getJobDataMap().get("elasticType");

		logger.info("对rc进行了一次自动的伸缩,apiServerUrl={}, 服务名:{}, podNum={}", apiServerUrl, dpName, podNum);
		try {
			KubeUtils.scaleDp("http://" + apiServerUrl, namespace, dpName, podNum);
			if (ELASTIC_TYPE_EXPAND.equals(elasticType)) {
				saveNotice(systemNoticeDao, createUserId,
						"根据指标为时间周期进行了一次自动伸缩,成功将服务名:" + dpName + "扩容到实例数为" + podNum + "个");
			} else {
				saveNotice(systemNoticeDao, createUserId,
						"根据指标为时间周期进行了一次自动伸缩,成功将服务名:" + dpName + "缩容到实例数为" + podNum + "个");
			}
		} catch (Exception e) {
			logger.error("自动伸缩时出现异常,服务名:" + dpName, e);
			if (ELASTIC_TYPE_EXPAND.equals(elasticType)) {
				saveNotice(systemNoticeDao, createUserId,
						"根据指标为时间周期进行了一次自动伸缩,将服务名:" + dpName + "扩容到实例数为" + podNum + "个,失败!");
			} else {
				saveNotice(systemNoticeDao, createUserId,
						"根据指标为时间周期进行了一次自动伸缩,将服务名:" + dpName + "缩容到实例数为" + podNum + "个,失败!");
			}
		}
	}

	/**
	 * 根据cpu的上限值启动自动伸缩（k8s1.4版本以上才有的功能）
	 * 
	 * @param policy
	 * @return void
	 * @author wupiao
	 * @since 2017年2月19日
	 */
	private void startAutoElasticByCpu(String apiServerUrl, String namespace
			, String dpName, AutoscalePolicy policy, Long createUserId) {
		String urlString = PropertiesUtils.getConfigValueCache("k8s.properties", "createAutoScale.api");
		urlString = MessageFormat.format(urlString, "http://" + apiServerUrl);

		final Map<String, Object> paramMap = getPodAutoscalerMap(namespace, dpName, policy);
		try {
			RestClientUtils.connect(urlString, new RestOperation() {

				@Override
				public Response request(WebTarget target) {
					return target.request().post(Entity.entity(paramMap, MediaType.APPLICATION_JSON_TYPE));
				}
			});
			saveNotice(createUserId, "成功根据指标为cpu创建了一次自动伸缩任务, 服务名:" + dpName);
		} catch (Exception e) {
			logger.error("根据cpu上限值创建自动伸缩 出现异常！", e);
			saveNotice(createUserId, "根据指标为cpu创建自动伸缩任务失败了, 服务名:" + dpName);
		}
	}

	private Map<String, Object> getPodAutoscalerMap(String namespace, String dpName, AutoscalePolicy policy) {
		Map<String, Object> map1 = new HashMap<>();
		map1.put("kind", "HorizontalPodAutoscaler");
		map1.put("apiVersion", "autoscaling/v1");
		Map<String, Object> map2 = new HashMap<>();
		map2.put("name", "autoscale-" + dpName);
		map2.put("namespace", namespace);
		map1.put("metadata", map2);

		Map<String, Object> map3 = new HashMap<>();
		map3.put("minReplicas", policy.getMinPodNum());
		map3.put("maxReplicas", policy.getMaxPodNum());
		map3.put("targetCPUUtilizationPercentage", policy.getMaxCpu());

		Map<String, Object> map4 = new HashMap<>();
		map4.put("kind", "Deployment");
		map4.put("name", dpName);

		map3.put("scaleTargetRef", map4);
		map1.put("spec", map3);

		return map1;
	}

	private void saveNotice(Long noticeUserId, String noticeContent) {
		saveNotice(systemNoticeDao, noticeUserId, noticeContent);
	}

	private void saveNotice(ISystemNoticeDao systemNoticeDao, Long noticeUserId, String noticeContent) {
		SystemNotice systemNotice = new SystemNotice();
		systemNotice.setNoticeType(NOTICE_TYPE);
		systemNotice.setNoticeTypeName(NOTICE_TYPE_NAME);
		systemNotice.setNoticeState(SystemNoticeConstant.NOT_READ);
		systemNotice.setNoticeContent(noticeContent);
		systemNotice.setNoticeUserId(noticeUserId);
		systemNotice.setNoticeTime(TimeUtils.getNowDateStr());

		systemNoticeDao.insert(systemNotice);
	}

	public static void main(String[] args) {
		String dateStr = "2017-02-11 10:01:02";
		Date date = TimeUtils.parse(dateStr);
		String year = TimeUtils.getYear(date);
		Integer month = TimeUtils.getMonth(date);
		Integer day = TimeUtils.getDay(date);
		Integer hour = TimeUtils.getHour(date);
		Integer minute = TimeUtils.getMinute(date);
		Integer second = TimeUtils.getSecond(date);
		String week = TimeUtils.getWeekOfDate(date);
		System.out.println(year);
		System.out.println(month);
		System.out.println(day);
		System.out.println(hour);
		System.out.println(minute);
		System.out.println(second);
		System.out.println(week);

		AutoElasticServiceImpl impl = new AutoElasticServiceImpl();
		String cron = impl.getCron("005", date.getTime() + "");
		System.out.println(cron);

		AutoscalePolicy policy = new AutoscalePolicy();
		policy.setMinPodNum(1);
		policy.setMaxPodNum(3);
		policy.setMaxCpu(10);
		// Map<String, Object> map = impl.getPodAutoscalerMap(policy);
		// System.out.println(JSONUtils.mapToJsonString(map));

	}

}
